package com.tencent.wxcloudrun.utils;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.tencent.wxcloudrun.response.AppException;
import com.tencent.wxcloudrun.response.ErrorCodeMsgEnum;

import java.text.ParseException;
import java.util.Date;

/**
 * ClassName: JwtToken
 * Package: com.tencent.wxcloudrun.utils
 * Description:
 * JWT（JSON Web Token）由三个部分组成：头部（Header）、载荷（Payload）和签名（Signature）。
 * 头部（Header）: 头部是一个包含令牌类型和签名算法的 JSON 对象。它指示了令牌的类型（通常为JWT）以及用于生成和验证签名的算法（例如HMAC、RSA等）。
 * 载荷（Payload）: 载荷是令牌的主要信息部分，用于存储各种声明（Claims），包括用户身份、权限等相关数据。载荷可以包含预定义的声明（如过期时间、发行者等），也可以包含自定义的声明。
 * 签名（Signature）: 签名是对头部、载荷以及使用密钥生成的哈希值进行签名的结果。签名用于验证令牌的完整性和真实性。接收到 JWT 后，接收方可以使用相同的密钥和算法重新计算签名，并比较其与令牌中的签名是否匹配，以确保令牌未被篡改。
 * 总结：头部指定了令牌的类型和签名算法，载荷存储了令牌的相关信息，而签名用于验证令牌的完整性和真实性。这三个部分共同构成了 JWT，使其成为一种安全的身份验证和授权机制。
 *
 * @Date: 2023/11/26 17:57
 * @Author: wht
 */
public class JwtToken {
    private static String SECRET = "Kas3#&*4J23@_+!4SDFdhjW+$%^@!5sfR";
    private static Integer expireTime = 30 * 60 * 1000;  // 半小时

    public static String getToken(Integer userId) throws JOSEException {
//          1.  创建JWT的头部（指定使用SHA-256算法进行签名）
        JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.HS256);

//          2.  创建JWT的载荷
//          2.1 new JWTClaimsSet.Builder()的作用：创建一个 JWTClaimsSet 对象的构建器（Builder），用于构建 JWT 的声明集合。）
        JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
        // 2.2 设置到期时间
        builder.expirationTime(new Date(ThreadLocalTool.getTime() + expireTime));
        // 2.3 添加载荷
        builder.subject(String.valueOf(userId));
        // 2.4 调用build()进行构建，得到最终的载荷数据JWTClaimsSet
        JWTClaimsSet jwtClaimsSet = builder.build();

//          2.2 创建一个JWT
        SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaimsSet);

//          3. 创建签名
//          3.1 创建HMAC签名器（ACS： 对称加密；RAS：非对称加密；）
        JWSSigner signer = new MACSigner(SECRET);
//          3.2.  使用之前创建的签名器对JWT进行签名。这将生成JWT的签名部分。
        signedJWT.sign(signer);

//          4. 将签名的JWT序列化为字符串
        String jwtString = signedJWT.serialize();

        return jwtString;
    }

    public static Integer parseToken(String token) throws ParseException, JOSEException {
//        先检查jwtString是否为空
        if (token == null || token.isEmpty()) {
            throw new AppException(ErrorCodeMsgEnum.TOKEN_ERROR);
        }

//      解析
        SignedJWT jwsObject = SignedJWT.parse(token);

//      验证
        JWSVerifier jwsVerifier = new MACVerifier(SECRET);
        if (!jwsObject.verify(jwsVerifier)) {
            throw new AppException(ErrorCodeMsgEnum.TOKEN_ERROR);
        }
//      判断是否过期
        Date expirationTime = jwsObject.getJWTClaimsSet().getExpirationTime();
        Long duration = expirationTime.getTime() - ThreadLocalTool.getTime()
        if (duration < 0) {
            throw new AppException(ErrorCodeMsgEnum.TOKEN_ERROR);
        }

//      获取载荷
        Integer userId = Integer.valueOf(jwsObject.getJWTClaimsSet().getSubject());


//        如果还差10分钟，就创建新的token，并保存到ThreadLocal中
        if (duration < 10 * 60 * 1000) {
            String newToken = getToken(userId);
            ThreadLocalTool.setToken(newToken);
        }
        return userId;
    }
}
